分类
联系方式
  1. 新浪微博
  2. E-mail

DartVM Timer

介绍

Dart 虚拟机中的定时器,代码位于 sdk/lib/async/timer.dart,这是一个抽象类。

有两种工作模式,单次触发和循环触发。

Timer 构造

位于 sdk/lib/async/timer.dart。

根据定时器的触发模式,有两种构造方式:

Timer 工厂构造方法:

  • 创建的是单次触发的定时器。
  • 这里面的具体 Timer 对象创建,是通过 Zone 来创建的,并且还对是否是 Root Zone 进行了区分。

对应代码:

factory Timer(Duration duration, void Function() callback) {
  if (Zone.current == Zone.root) {
    return Zone.current.createTimer(duration, callback);
  }
  return Zone.current
             .createTimer(
                 duration, 
                 Zone.current.bindCallbackGuarded(callback));
}

Timer.periodic 工厂构造方法:

  • 创建的是重复触发的定时器。
  • 跟单次的类似,也是通过 Zone 进行创建。

createTimer 底层调用链梳理

一、Timer 构造函数

从上面代码中摘出这一行:

Zone.current.createTimer(duration, callback);

来到 sdk/lib/async/zone.dart 的 Zone 抽象类的 createTimer 抽象方法:

Timer createTimer(Duration duration, void Function() callback);

这里以 Root Zone 为例,看 _RootZone 的 createTimer:

Timer createTimer(Duration duration, void f()) {
  return Timer._createTimer(duration, f);
}

回到 timer.dart 的 _createTimer 方法:

external static Timer _createTimer(Duration duration, void Function() callback);
external static Timer _createPeriodicTimer(Duration duration, void callback(Timer timer));

这是一个 external 方法,实现位于 sdk/lib/_internal/vm/lib/timer_patch.dart 的 Timer 类:

@patch
class Timer {
  @patch
  static Timer _createTimer(Duration duration, void callback()) {
    final factory = VMLibraryHooks.timerFactory;
    if (factory == null) {
      throw new UnsupportedError("Timer interface not supported.");
    }
    int milliseconds = duration.inMilliseconds;
    if (milliseconds < 0) milliseconds = 0;
    return factory(
        milliseconds, 
        (_) { callback();}, 
        false);
  }

关键方法是这个 factory。factory 哪里注入的?在 /sdk/lib/_internal/vm/lib/timer_impl.dart,里面声明了一个全局入口方法 _setupHooks:

@pragma("vm:entry-point", "call")
_setupHooks() {
  VMLibraryHooks.timerFactory = _Timer._factory;
}

可以看到,最终实现方法是 _Timer 的 _factory。这个方法实现在同一个文件中:

// The Timer factory registered with the dart:async library by the embedder.
static Timer _factory(
  int milliSeconds, void callback(Timer timer), bool repeating) {
  if (repeating) {
    return new _Timer.periodic(milliSeconds, callback);
  }
  return new _Timer(milliSeconds, callback);
}

这里以单次触发的定时器为例。对照上面的 factory 传参来看。

  • milliseconds:定时器延时
  • callback:定时器触发回调,这个 callback 网上追溯,是开发者传入的 callback
  • repeating:单次触发还是循环触发

接下来的操作进入 _Timer 类一节来看了。

还有一点需要注意,在 Dart 中,创建好定时器 Timer 后,并不会自动开始,需要调用 start 方法后,才会开始定时器操作。

_Timer 类

这个类位于 /sdk/lib/_internal/vm/lib/timer_impl.dart。Timer 是一个抽象类,_Timer 是其实现类。

二叉堆

Dart 程序中包含多个定时器,在 DartVM 需要进行统一维护,根据定时器的预订时间进行触发调度。为了记录这些定时器的时间,采用二叉堆数据结构进行维护。

对应的成员是:

static final _heap = new _TimerHeap();

_createTimer

在上面的代码中,通过 _Timer 的构造函数返回 Timer 对象,对应的实现是:

factory _Timer(int milliSeconds, void callback(Timer timer)) {
  return _createTimer(callback, milliSeconds, false);
}

可以看到,实际实现在 _createTimer 方法:

static _Timer _createTimer(
    void callback(Timer timer), int milliSeconds, bool repeating) {
  // 如果定时器延时小于0,认为就是0
  if (milliSeconds < 0) {
    milliSeconds = 0;
  }
  // 获取当前时间戳
  int now = VMLibraryHooks.timerMillisecondClock();
  int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
  
  // 通过 _Timer._internal 创建实际 _Timer 实例
  _Timer timer =
      new _Timer._internal(callback, wakeupTime, milliSeconds, repeating);
      
  // 有一个入队操作
  timer._enqueue();
  return timer;
}

先看 _Timer 实例的创建:

_Timer._internal(
    this._callback, this._wakeupTime, this._milliSeconds, this._repeating)
    : _id = _nextId();

可以看到,这个构造函数以保存构造参数为主,同时自动创建了一个 id。 接下来再看这个入队操作:

void _enqueue() {
  if (_milliSeconds == 0) {
    if (_firstZeroTimer == null) {
      _lastZeroTimer = this;
      _firstZeroTimer = this;
    } else {
      _lastZeroTimer._indexOrNext = this;
      _lastZeroTimer = this;
    }
    // Every zero timer gets its own event.
    _notifyZeroHandler();
  } else {
    _heap.add(this);
    if (_heap.isFirst(this)) {
      _notifyEventHandler();
    }
  }
}

其中:

  • 如果定时器延时为 0,是将定时器加入到一个全局链表当中
  • 如果定时器延时大于0,则添加到二叉堆当中

这里有两个通知函数:

  • _notifyZeroHandler
  • _notifyEventHandler

这里的通知机制,是接下来需要看的重点。

小细节

设置一个负数延时,相当于延时为 0。